package com.tianji.promotion.service.impl;

import cn.hutool.core.bean.copier.CopyOptions;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.autoconfigure.redisson.annotations.Lock;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.exceptions.DbException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.constants.PromotionConstants;
import com.tianji.promotion.domain.dto.CouponDiscountDTO;
import com.tianji.promotion.domain.dto.OrderCourseDTO;
import com.tianji.promotion.domain.dto.UserCouponDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.ExchangeCode;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.CouponQuery;
import com.tianji.promotion.domain.vo.CouponDetailVO;
import com.tianji.promotion.domain.vo.CouponPageVO;
import com.tianji.promotion.enums.ExchangeCodeStatus;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.IDiscountService;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.promotion.strategy.discount.Discount;
import com.tianji.promotion.strategy.discount.DiscountStrategy;
import com.tianji.promotion.utils.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AopContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.tianji.promotion.enums.UserCouponStatus.EXPIRED;

/**
 * <p>
 * 用户领取优惠券的记录，是真正使用的优惠券信息 服务实现类
 * </p>
 *
 * @author 赵博凯
 * @since 2025-11-03
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class UserCouponServiceImpl extends ServiceImpl<UserCouponMapper, UserCoupon> implements IUserCouponService {


    private final CouponMapper couponMapper;
    private final IExchangeCodeService codeService;
    private final IDiscountService discountService;
    private final RabbitMqHelper mqHelper;
    private final RedisTemplate redisTemplate;

    @Override
    public void receiveCoupon(Long couponId) {
        // 使用分布式锁，确保在分布式环境中的线程安全
        UserCouponServiceImpl userCouponService = getCurrentProxy();
        userCouponService.receiveCouponWithLock(couponId);
    }

    @Lock(name = "lock:coupon:{couponId}")
    public void receiveCouponWithLock(Long couponId) {

        // 1.查询优惠券
        Coupon coupon = queryCouponByCache(couponId);
        if (coupon == null) {
            throw new BadRequestException("优惠券不存在");
        }
        // 2.校验发放时间
        LocalDateTime now = LocalDateTime.now();
        // 添加空值检查，避免NPE
        if (coupon.getIssueBeginTime() == null || coupon.getIssueEndTime() == null ||
            now.isBefore(coupon.getIssueBeginTime()) || now.isAfter(coupon.getIssueEndTime())) {
            throw new BadRequestException("优惠券发放已经结束或尚未开始");
        }
        // 3.校验库存（先从缓存中扣减库存）
        // 添加空值检查，避免NPE
        if (coupon.getTotalNum() == null) {
            throw new BadRequestException("优惠券库存信息不完整");
        }
        
        // 从缓存中扣减库存并检查是否超出库存
        Long remainingNum = incrementCacheFieldWithRetry(couponId, "totalNum", -1);
        if (remainingNum < 0) {
            // 如果库存不足，将库存加回去
            redisTemplate.opsForHash().increment(
                    PromotionConstants.COUPON_CACHE_KEY_PREFIX + couponId, "totalNum", 1);
            throw new BadRequestException("优惠券库存不足");
        }

        Long userId = UserContext.getUser();
        // 4.校验每人限领数量
        // 4.1.查询领取数量
        String key = PromotionConstants.USER_COUPON_CACHE_KEY_PREFIX + couponId;
        Long count = incrementCacheFieldWithRetry(key, userId.toString(), 1);
        
        // 4.2.校验限领数量
        // 添加空值检查，避免NPE
        if(coupon.getUserLimit() == null || count > coupon.getUserLimit()){
            // 超出限领数量，将用户领取计数减回去
            redisTemplate.opsForHash().increment(key, userId.toString(), -1);
            // 库存也加回去
            redisTemplate.opsForHash().increment(
                    PromotionConstants.COUPON_CACHE_KEY_PREFIX + couponId, "totalNum", 1);
            throw new BadRequestException("超出领取数量");
        }

        // 5.发送MQ消息
        UserCouponDTO uc = new UserCouponDTO();
        uc.setUserId(userId);
        uc.setCouponId(couponId);
        mqHelper.send(MqConstants.Exchange.PROMOTION_EXCHANGE, MqConstants.Key.COUPON_RECEIVE, uc);
    }

    /**
     * 带重试机制的缓存字段自增方法
     * @param key Redis key
     * @param field hash field
     * @param delta 增量
     * @return 自增后的值
     */
    private Long incrementCacheFieldWithRetry(String key, String field, long delta) {
        try {
            return redisTemplate.opsForHash().increment(key, field, delta);
        } catch (Exception e) {
            // 如果出现类型转换异常，说明缓存数据有问题，需要清除缓存并从数据库重新加载
            redisTemplate.delete(key);
            throw new BadRequestException("系统错误，请稍后重试");
        }
    }

    /**
     * 带重试机制的缓存字段自增方法（针对优惠券）
     * @param couponId 优惠券ID
     * @param field hash field
     * @param delta 增量
     * @return 自增后的值
     */
    private Long incrementCacheFieldWithRetry(Long couponId, String field, long delta) {
        String key = PromotionConstants.COUPON_CACHE_KEY_PREFIX + couponId;
        try {
            return redisTemplate.opsForHash().increment(key, field, delta);
        } catch (Exception e) {
            // 如果出现类型转换异常，说明缓存数据有问题，需要清除缓存并从数据库重新加载
            redisTemplate.delete(key);
            // 重新从数据库查询并更新缓存
            Coupon freshCoupon = couponMapper.selectById(couponId);
            if (freshCoupon == null) {
                throw new BadRequestException("优惠券不存在");
            }
            cacheCouponInfo(freshCoupon);
            
            // 再次尝试扣减库存
            if (freshCoupon.getTotalNum() == null) {
                throw new BadRequestException("优惠券库存信息不完整");
            }
            
            // 直接返回数据库中的库存值减去delta，而不是再次尝试increment操作
            // 因为刚重建的缓存应该是正确的，如果还有问题，说明是系统性问题
            if ("totalNum".equals(field)) {
                return (long)freshCoupon.getTotalNum() + delta;
            } else if ("issueNum".equals(field)) {
                return (long)(freshCoupon.getIssueNum() != null ? freshCoupon.getIssueNum() : 0) + delta;
            } else {
                // 对于其他字段，我们尝试直接设置值而不是自增
                String valueStr = (String)redisTemplate.opsForHash().get(key, field);
                try {
                    long value = valueStr != null ? Long.parseLong(valueStr) : 0;
                    long newValue = value + delta;
                    redisTemplate.opsForHash().put(key, field, String.valueOf(newValue));
                    return newValue;
                } catch (NumberFormatException ex) {
                    // 如果还是不能转换为数字，直接使用delta作为值
                    redisTemplate.opsForHash().put(key, field, String.valueOf(delta));
                    return delta;
                }
            }
        }
    }

    // 获取当前代理对象的安全方法
    private UserCouponServiceImpl getCurrentProxy() {
        try {
            return (UserCouponServiceImpl) AopContext.currentProxy();
        } catch (IllegalStateException e) {
            // 如果无法获取代理对象，则返回当前实例
            return this;
        }
    }

    @Transactional
    @Override
    public void checkAndCreateUserCoupon(UserCouponDTO uc) {
        // 1.查询优惠券
        Coupon coupon = couponMapper.selectById(uc.getCouponId());
        if (coupon == null) {
            throw new BizIllegalException("优惠券不存在！");
        }
        // 2.更新优惠券的已经发放的数量 + 1
        int r = couponMapper.incrIssueNum(coupon.getId());
        if (r == 0) {
            throw new BizIllegalException("优惠券库存不足！");
        }
        // 3.新增一个用户券
        saveUserCoupon(coupon, uc.getUserId());
        // 4.更新兑换码状态
        if (uc.getSerialNum()!= null) {
            codeService.lambdaUpdate()
                    .set(ExchangeCode::getUserId, uc.getUserId())
                    .set(ExchangeCode::getStatus, ExchangeCodeStatus.USED)
                    .eq(ExchangeCode::getId, uc.getSerialNum())
                    .update();
        }
    }

    @Override
    public PageDTO<CouponDetailVO> queryUserCouponPage(CouponQuery query) {
        Integer status = query.getStatus();

        //1.分页查询
        Page<UserCoupon> page = lambdaQuery()
                .eq(status != null, UserCoupon::getStatus, status)
                .eq(UserCoupon::getUserId, UserContext.getUser())
                .page(query.toMpPageDefaultSortByCreateTimeDesc());

        //2.处理vo
        List<UserCoupon> records = page.getRecords();
        List<CouponDetailVO> list = BeanUtils.copyList(records, CouponDetailVO.class);

        // 批量查询所有需要的优惠券信息
        if (!records.isEmpty()) {
            List<Long> couponIds = records.stream()
                    .map(UserCoupon::getCouponId)
                    .distinct()
                    .collect(Collectors.toList());

            List<Coupon> coupons = couponMapper.selectList(new LambdaQueryWrapper<Coupon>()
                    .in(Coupon::getId, couponIds));
            Map<Long, Coupon> couponMap = coupons.stream()
                    .collect(Collectors.toMap(Coupon::getId, coupon -> coupon));

            list.forEach(vo -> {
                // 通过records列表找到对应的UserCoupon对象
                UserCoupon userCoupon = records.stream()
                        .filter(uc -> uc.getId().equals(vo.getId()))
                        .findFirst()
                        .orElse(null);

                if (userCoupon != null) {
                    // 检查是否过期，如果过期则更新状态
                    if (userCoupon.getTermEndTime() != null &&
                            userCoupon.getTermEndTime().isBefore(LocalDateTime.now()) &&
                            !EXPIRED.equals(userCoupon.getStatus())) {
                        userCoupon.setStatus(EXPIRED);
                        lambdaUpdate()
                                .set(UserCoupon::getStatus, EXPIRED)
                                .eq(UserCoupon::getId, userCoupon.getId())
                                .update();
                    }

                    // 即使是已过期的优惠券也需要设置相关信息
                    Coupon coupon = couponMap.get(userCoupon.getCouponId());
                    if (coupon != null) {
                        vo.setName(coupon.getName());
                        vo.setSpecific(coupon.getSpecific());
                        vo.setDiscountType(coupon.getDiscountType());
                        vo.setThresholdAmount(coupon.getThresholdAmount());
                        vo.setDiscountValue(coupon.getDiscountValue());
                        vo.setMaxDiscountAmount(coupon.getMaxDiscountAmount());
                    }
                }
            });

        }

        //3.返回
        return PageDTO.of(page, list);
    }


    @Override
    @Transactional
    public void exchangeCoupon(String code) {
        // 1.校验并解析兑换码
        long serialNum = CodeUtil.parseCode(code);
        // 2.校验是否已经兑换 SETBIT KEY 4 1 ，这里直接执行setbit，通过返回值来判断是否兑换过
        boolean exchanged = codeService.updateExchangeMark(serialNum, true);
        if (exchanged) {
            throw new BizIllegalException("兑换码已经被兑换过了");
        }
        try {
            // 3.查询兑换码对应的优惠券id
            ExchangeCode exchangeCode = codeService.getById(serialNum);
            if (exchangeCode == null) {
                throw new BizIllegalException("兑换码不存在！");
            }
            // 4.是否过期
            LocalDateTime now = LocalDateTime.now();
            if (now.isAfter(exchangeCode.getExpiredTime())) {
                throw new BizIllegalException("兑换码已经过期");
            }
            // 5.校验并生成用户券
            // 5.1.查询优惠券
            Coupon coupon = couponMapper.selectById(exchangeCode.getExchangeTargetId());
            if (coupon == null) {
                throw new BizIllegalException("优惠券不存在！");
            }
            // 5.2.查询用户
            UserCouponDTO uc = new UserCouponDTO();
            uc.setCouponId(exchangeCode.getExchangeTargetId());
            uc.setUserId(UserContext.getUser());
            uc.setSerialNum((int) serialNum);
            // 5.3.校验并生成用户券，更新兑换码状态
            checkAndCreateUserCoupon(uc);
        } catch (Exception e) {
            // 重置兑换的标记 0
            codeService.updateExchangeMark(serialNum, false);
            throw e;
        }
    }

    @Override
    @Transactional
    public void writeOffCoupon(List<Long> couponIds) {
        // 1.检查参数
        if (CollUtils.isEmpty(couponIds)) {
            return;
        }
        // 2.查询优惠券
        List<UserCoupon> userCoupons = listByIds(couponIds);
        if (CollUtils.isEmpty(userCoupons)) {
            return;
        }
        // 3.处理数据
        List<UserCoupon> list = userCoupons.stream()
                // 过滤无效券
                .filter(coupon -> {
                    if (coupon == null) {
                        return false;
                    }
                    if (UserCouponStatus.UNUSED != coupon.getStatus()) {
                        return false;
                    }
                    LocalDateTime now = LocalDateTime.now();
                    return !now.isBefore(coupon.getTermBeginTime()) && !now.isAfter(coupon.getTermEndTime());
                })
                // 组织新增数据
                .map(coupon -> {
                    UserCoupon c = new UserCoupon();
                    c.setId(coupon.getId());
                    c.setStatus(UserCouponStatus.USED);
                    return c;
                })
                .collect(Collectors.toList());

        // 4.核销，修改优惠券状态
        boolean success = updateBatchById(list);
        if (!success) {
            return;
        }
        // 5.更新已使用数量
        List<Long> useCouponIds = userCoupons.stream().map(UserCoupon::getCouponId).collect(Collectors.toList());
        int c = couponMapper.incrIssueNumBatch(useCouponIds, 1);
        if (c < 1) {
            throw new DbException("更新优惠券使用数量失败！");
        }
    }


    @Override
    @Transactional
    public void refundCoupon(List<Long> couponIds) {
        // 1.检查参数
        if (CollUtils.isEmpty(couponIds)) {
            return;
        }
        // 2.查询优惠券
        List<UserCoupon> userCoupons = listByIds(couponIds);
        if (CollUtils.isEmpty(userCoupons)) {
            return;
        }
        // 3.处理优惠券数据
        List<UserCoupon> list = userCoupons.stream()
                // 过滤无效券
                .filter(coupon -> coupon != null && UserCouponStatus.USED == coupon.getStatus())
                // 更新状态字段
                .map(coupon -> {
                    UserCoupon c = new UserCoupon();
                    c.setId(coupon.getId());
                    // 4.判断有效期，是否已经过期，如果过期，则状态为 已过期，否则状态为 未使用
                    LocalDateTime now = LocalDateTime.now();
                    UserCouponStatus status = now.isAfter(coupon.getTermEndTime()) ?
                            UserCouponStatus.EXPIRED : UserCouponStatus.UNUSED;
                    c.setStatus(status);
                    return c;
                }).collect(Collectors.toList());

        // 5.修改优惠券状态
        boolean success = updateBatchById(list);
        if (!success) {
            return;
        }
        // 6.更新已使用数量
        List<Long> useCouponIds = userCoupons.stream().map(UserCoupon::getCouponId).collect(Collectors.toList());
        int c = couponMapper.incrUsedNum(useCouponIds, -1);
        if (c < 1) {
            throw new DbException("更新优惠券使用数量失败！");
        }
    }


    @Override
    public List<String> queryDiscountRules(List<Long> couponIds) {
        // 1.查询优惠券信息
        List<Coupon> coupons = baseMapper.queryCouponByUserCouponIds(couponIds, UserCouponStatus.USED);
        if (CollUtils.isEmpty(coupons)) {
            return CollUtils.emptyList();
        }
        // 2.转换规则
        return coupons.stream()
                .map(c -> DiscountStrategy.getDiscount(c.getDiscountType()).getRule(c))
                .collect(Collectors.toList());
    }
    
    @Override
    public List<CouponDiscountDTO> queryAvailableCoupons(Long userId, List<OrderCourseDTO> orderCourses) {
        // 使用 UserContext 设置当前用户 ID，确保 DiscountServiceImpl 中能正确获取
        UserContext.setUser(userId);
        try {
            // 调用 DiscountServiceImpl 的 findDiscountSolution 方法计算可用优惠券方案
            return discountService.findDiscountSolution(orderCourses);
        } finally {
            // 清理 UserContext 避免内存泄漏
            UserContext.removeUser();
        }
    }

    private Coupon queryCouponByCache(Long couponId) {

        // 1.准备KEY
        String key = PromotionConstants.COUPON_CACHE_KEY_PREFIX + couponId;
        // 2.查询
        Map<Object, Object> map = redisTemplate.opsForHash().entries(key);
        if (map.isEmpty()) {
            // 缓存中没有数据，从数据库查询
            Coupon coupon = couponMapper.selectById(couponId);
            if (coupon != null) {
                // 查询到数据，重新写入缓存
                cacheCouponInfo(coupon);
            }
            return coupon;
        }

        // 3.处理时间字段，避免Hutool解析错误
        Object issueBeginTimeObj = map.get("issueBeginTime");
        Object issueEndTimeObj = map.get("issueEndTime");
        
        if (issueBeginTimeObj instanceof String) {
            try {
                long timestamp = Long.parseLong((String) issueBeginTimeObj);
                // 直接转换为LocalDateTime，不使用DateUtils.getAsiaTime以避免时区转换问题
                LocalDateTime issueBeginTime = Instant.ofEpochMilli(timestamp)
                        .atZone(ZoneId.systemDefault())
                        .toLocalDateTime();
                map.put("issueBeginTime", issueBeginTime);
            } catch (NumberFormatException e) {
                // 如果转换失败，保持原值，让后续处理
            }
        }
        
        if (issueEndTimeObj instanceof String) {
            try {
                long timestamp = Long.parseLong((String) issueEndTimeObj);
                // 直接转换为LocalDateTime，不使用DateUtils.getAsiaTime以避免时区转换问题
                LocalDateTime issueEndTime = Instant.ofEpochMilli(timestamp)
                        .atZone(ZoneId.systemDefault())
                        .toLocalDateTime();
                map.put("issueEndTime", issueEndTime);
            } catch (NumberFormatException e) {
                // 如果转换失败，保持原值，让后续处理
            }
        }

        // 4.检查必要字段是否存在，如果不存在则从数据库查询完整数据
        if (!map.containsKey("totalNum") || !map.containsKey("userLimit")) {
            // 缓存数据不完整，从数据库查询完整数据
            Coupon coupon = couponMapper.selectById(couponId);
            if (coupon != null) {
                // 更新缓存
                cacheCouponInfo(coupon);
            }
            return coupon;
        }

        // 5.数据反序列化
        Coupon coupon = BeanUtils.mapToBean(map, Coupon.class, false, CopyOptions.create());
        return coupon;
    }


    private void saveUserCoupon(Coupon coupon, Long userId) {
        // 1.基本信息
        UserCoupon uc = new UserCoupon();
        uc.setUserId(userId);
        uc.setCouponId(coupon.getId());
        // 2.有效期信息
        LocalDateTime termBeginTime = coupon.getTermBeginTime();
        LocalDateTime termEndTime = coupon.getTermEndTime();
        if (termBeginTime == null) {
            termBeginTime = LocalDateTime.now();
            termEndTime = termBeginTime.plusDays(coupon.getTermDays());
        }
        uc.setTermBeginTime(termBeginTime);
        uc.setTermEndTime(termEndTime);
        // 3.保存
        save(uc);
    }

    private void cacheCouponInfo(Coupon coupon) {
        // 1.组织数据
        Map<String, String> map = new HashMap<>(4);
        map.put("issueBeginTime", String.valueOf(coupon.getIssueBeginTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()));
        map.put("issueEndTime", String.valueOf(coupon.getIssueEndTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()));
        map.put("totalNum", String.valueOf(coupon.getTotalNum()));
        map.put("userLimit", String.valueOf(coupon.getUserLimit()));
        // 2.写缓存
        redisTemplate.opsForHash().putAll(PromotionConstants.COUPON_CACHE_KEY_PREFIX + coupon.getId(), map);
    }

}